--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
visualiser-wasm/internal/scene/scene.go 37edfa4cfe5aa2f6ebb7e4aeac2e2c67ad181a52 (37edfa4c) Text, 9.27 KB
T8b949e// SPDX-License-Identifier: 0BSD
T8b949e// Package scene holds an interactive mesh graph for the WebGL renderer.
T8b949e// Positions and force ticks run in WASM. Pixel draw stays in JS WebGL.
Tff7b72package Te6edf3scene
Tff7b72import Tb4b4b4(
Ta5d6ff"math"
Ta5d6ff"github.com/Quad4-Software/MeshChatX/visualiser-wasm/internal/layout"
Tb4b4b4)
T8b949e// Kind constants packed into draw buffers.
Tff7b72const Tb4b4b4(
Te6edf3KindMe Tb4b4b4= T79c0ff0
Te6edf3KindIfaceOn Tb4b4b4= T79c0ff1
Te6edf3KindIfaceOff Tb4b4b4= T79c0ff2
Te6edf3KindPeer Tb4b4b4= T79c0ff3
Te6edf3KindDiscovered Tb4b4b4= T79c0ff4
Tb4b4b4)
T8b949e// NodeStride floats per node draw record: x y size r g b a kind
Tff7b72const Te6edf3NodeStride Tb4b4b4= T79c0ff8
T8b949e// EdgeStride floats per edge draw record: x1 y1 x2 y2 r g b a
Tff7b72const Te6edf3EdgeStride Tb4b4b4= T79c0ff8
T8b949e// Node is one drawable / simulatable body.
Tff7b72type Te6edf3Node Tff7b72struct Tb4b4b4{
Te6edf3ID Tffa657string Ta5d6ff`json:"id"`
Te6edf3X Tffa657float64 Ta5d6ff`json:"x"`
Te6edf3Y Tffa657float64 Ta5d6ff`json:"y"`
Te6edf3Mass Tffa657float64 Ta5d6ff`json:"mass"`
Te6edf3Fixed Tffa657bool Ta5d6ff`json:"fixed"`
Te6edf3Kind Tffa657int Ta5d6ff`json:"kind"`
Te6edf3Size Tffa657float64 Ta5d6ff`json:"size"`
Te6edf3R Tffa657float64 Ta5d6ff`json:"r"`
Te6edf3G Tffa657float64 Ta5d6ff`json:"g"`
Te6edf3B Tffa657float64 Ta5d6ff`json:"b"`
Te6edf3A Tffa657float64 Ta5d6ff`json:"a"`
Tb4b4b4}
T8b949e// Edge connects two node ids.
Tff7b72type Te6edf3Edge Tff7b72struct Tb4b4b4{
Te6edf3From Tffa657string Ta5d6ff`json:"from"`
Te6edf3To Tffa657string Ta5d6ff`json:"to"`
Te6edf3Width Tffa657float64 Ta5d6ff`json:"width"`
Te6edf3R Tffa657float64 Ta5d6ff`json:"r"`
Te6edf3G Tffa657float64 Ta5d6ff`json:"g"`
Te6edf3B Tffa657float64 Ta5d6ff`json:"b"`
Te6edf3A Tffa657float64 Ta5d6ff`json:"a"`
Tb4b4b4}
T8b949e// SetRequest replaces scene contents.
Tff7b72type Te6edf3SetRequest Tff7b72struct Tb4b4b4{
Te6edf3Nodes Tb4b4b4[Tb4b4b4]Te6edf3Node Ta5d6ff`json:"nodes"`
Te6edf3Edges Tb4b4b4[Tb4b4b4]Te6edf3Edge Ta5d6ff`json:"edges"`
Te6edf3Width Tffa657float64 Ta5d6ff`json:"width"`
Te6edf3Height Tffa657float64 Ta5d6ff`json:"height"`
Te6edf3CamX Tffa657float64 Ta5d6ff`json:"cam_x"`
Te6edf3CamY Tffa657float64 Ta5d6ff`json:"cam_y"`
Te6edf3Zoom Tffa657float64 Ta5d6ff`json:"zoom"`
Tb4b4b4}
T8b949e// CameraState is the 2D view transform (world -> screen via zoom/pan).
Tff7b72type Te6edf3CameraState Tff7b72struct Tb4b4b4{
Te6edf3X Tffa657float64 Ta5d6ff`json:"x"`
Te6edf3Y Tffa657float64 Ta5d6ff`json:"y"`
Te6edf3Zoom Tffa657float64 Ta5d6ff`json:"zoom"`
Tb4b4b4}
T8b949e// Scene is the mutable WASM-side graph for WebGL.
Tff7b72type Te6edf3Scene Tff7b72struct Tb4b4b4{
Te6edf3nodes Tb4b4b4[Tb4b4b4]Te6edf3Node
Te6edf3edges Tb4b4b4[Tb4b4b4]Te6edf3Edge
Te6edf3index Tff7b72mapTb4b4b4[Tffa657stringTb4b4b4]Tffa657int
Te6edf3vx Tb4b4b4[Tb4b4b4]Tffa657float64
Te6edf3vy Tb4b4b4[Tb4b4b4]Tffa657float64
Te6edf3width Tffa657float64
Te6edf3height Tffa657float64
Te6edf3camX Tffa657float64
Te6edf3camY Tffa657float64
Te6edf3zoom Tffa657float64
Te6edf3dragIdx Tffa657int
Tb4b4b4}
T8b949e// New returns an empty scene centred on the origin.
Tff7b72func Te6edf3NewTb4b4b4(Tb4b4b4) Tff7b72*Te6edf3Scene Tb4b4b4{
Tff7b72return Tff7b72&Te6edf3SceneTb4b4b4{
Te6edf3indexTb4b4b4: Tff7b72mapTb4b4b4[Tffa657stringTb4b4b4]Tffa657intTb4b4b4{Tb4b4b4}Tb4b4b4,
Te6edf3widthTb4b4b4: T79c0ff800Tb4b4b4,
Te6edf3heightTb4b4b4: T79c0ff600Tb4b4b4,
Te6edf3zoomTb4b4b4: T79c0ff1Tb4b4b4,
Te6edf3dragIdxTb4b4b4: Tff7b72-T79c0ff1Tb4b4b4,
Tb4b4b4}
Tb4b4b4}
T8b949e// Set replaces nodes and edges. Preserves camera unless zoom is > 0 in req.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3SetTb4b4b4(Te6edf3req Te6edf3SetRequestTb4b4b4) Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4= Tffa657appendTb4b4b4(Tb4b4b4[Tb4b4b4]Te6edf3NodeTb4b4b4(Tff7b72nilTb4b4b4)Tb4b4b4, Te6edf3reqTb4b4b4.Te6edf3NodesTff7b72...Tb4b4b4)
Te6edf3sTb4b4b4.Te6edf3edges Tb4b4b4= Tffa657appendTb4b4b4(Tb4b4b4[Tb4b4b4]Te6edf3EdgeTb4b4b4(Tff7b72nilTb4b4b4)Tb4b4b4, Te6edf3reqTb4b4b4.Te6edf3EdgesTff7b72...Tb4b4b4)
Te6edf3sTb4b4b4.Te6edf3index Tb4b4b4= Tffa657makeTb4b4b4(Tff7b72mapTb4b4b4[Tffa657stringTb4b4b4]Tffa657intTb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Te6edf3n Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]
Tff7b72if Te6edf3nTb4b4b4.Te6edf3Mass Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3nTb4b4b4.Te6edf3Mass Tb4b4b4= T79c0ff1
Tb4b4b4}
Tff7b72if Te6edf3nTb4b4b4.Te6edf3Size Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3nTb4b4b4.Te6edf3Size Tb4b4b4= Te6edf3defaultSizeTb4b4b4(Te6edf3nTb4b4b4.Te6edf3KindTb4b4b4)
Tb4b4b4}
Tff7b72if Te6edf3nTb4b4b4.Te6edf3A Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3nTb4b4b4.Te6edf3A Tb4b4b4= T79c0ff1
Tb4b4b4}
Tff7b72if Te6edf3nTb4b4b4.Te6edf3R Tff7b72== T79c0ff0 Tff7b72&& Te6edf3nTb4b4b4.Te6edf3G Tff7b72== T79c0ff0 Tff7b72&& Te6edf3nTb4b4b4.Te6edf3B Tff7b72== T79c0ff0 Tb4b4b4{
Te6edf3nTb4b4b4.Te6edf3RTb4b4b4, Te6edf3nTb4b4b4.Te6edf3GTb4b4b4, Te6edf3nTb4b4b4.Te6edf3B Tb4b4b4= Te6edf3defaultColorTb4b4b4(Te6edf3nTb4b4b4.Te6edf3KindTb4b4b4)
Tb4b4b4}
Tff7b72if Te6edf3nTb4b4b4.Te6edf3ID Tff7b72== Ta5d6ff"me" Tb4b4b4{
Te6edf3nTb4b4b4.Te6edf3Fixed Tb4b4b4= Tff7b72true
Te6edf3nTb4b4b4.Te6edf3Kind Tb4b4b4= Te6edf3KindMe
Tb4b4b4}
Te6edf3sTb4b4b4.Te6edf3indexTb4b4b4[Te6edf3nTb4b4b4.Te6edf3IDTb4b4b4] Tb4b4b4= Te6edf3i
Tb4b4b4}
Tff7b72if Te6edf3reqTb4b4b4.Te6edf3Width Tb4b4b4> T79c0ff0 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3width Tb4b4b4= Te6edf3reqTb4b4b4.Te6edf3Width
Tb4b4b4}
Tff7b72if Te6edf3reqTb4b4b4.Te6edf3Height Tb4b4b4> T79c0ff0 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3height Tb4b4b4= Te6edf3reqTb4b4b4.Te6edf3Height
Tb4b4b4}
Tff7b72if Te6edf3reqTb4b4b4.Te6edf3Zoom Tb4b4b4> T79c0ff0 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4= Te6edf3reqTb4b4b4.Te6edf3Zoom
Te6edf3sTb4b4b4.Te6edf3camX Tb4b4b4= Te6edf3reqTb4b4b4.Te6edf3CamX
Te6edf3sTb4b4b4.Te6edf3camY Tb4b4b4= Te6edf3reqTb4b4b4.Te6edf3CamY
Tb4b4b4}
Te6edf3sTb4b4b4.Te6edf3dragIdx Tb4b4b4= Tff7b72-T79c0ff1
Te6edf3sTb4b4b4.Te6edf3vx Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float64Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Te6edf3sTb4b4b4.Te6edf3vy Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float64Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72func Te6edf3defaultSizeTb4b4b4(Te6edf3kind Tffa657intTb4b4b4) Tffa657float64 Tb4b4b4{
Tff7b72switch Te6edf3kind Tb4b4b4{
Tff7b72case Te6edf3KindMeTb4b4b4:
Tff7b72return T79c0ff18
Tff7b72case Te6edf3KindIfaceOnTb4b4b4, Te6edf3KindIfaceOffTb4b4b4:
Tff7b72return T79c0ff12
Tff7b72case Te6edf3KindDiscoveredTb4b4b4:
Tff7b72return T79c0ff9
Tff7b72defaultTb4b4b4:
Tff7b72return T79c0ff10
Tb4b4b4}
Tb4b4b4}
Tff7b72func Te6edf3defaultColorTb4b4b4(Te6edf3kind Tffa657intTb4b4b4) Tb4b4b4(Te6edf3rTb4b4b4, Te6edf3gTb4b4b4, Te6edf3b Tffa657float64Tb4b4b4) Tb4b4b4{
Tff7b72switch Te6edf3kind Tb4b4b4{
Tff7b72case Te6edf3KindMeTb4b4b4:
Tff7b72return T79c0ff0.23Tb4b4b4, T79c0ff0.51Tb4b4b4, T79c0ff0.96
Tff7b72case Te6edf3KindIfaceOnTb4b4b4:
Tff7b72return T79c0ff0.06Tb4b4b4, T79c0ff0.73Tb4b4b4, T79c0ff0.51
Tff7b72case Te6edf3KindIfaceOffTb4b4b4:
Tff7b72return T79c0ff0.45Tb4b4b4, T79c0ff0.45Tb4b4b4, T79c0ff0.50
Tff7b72case Te6edf3KindDiscoveredTb4b4b4:
Tff7b72return T79c0ff0.66Tb4b4b4, T79c0ff0.33Tb4b4b4, T79c0ff0.97
Tff7b72defaultTb4b4b4:
Tff7b72return T79c0ff0.85Tb4b4b4, T79c0ff0.85Tb4b4b4, T79c0ff0.90
Tb4b4b4}
Tb4b4b4}
T8b949e// Resize updates the viewport size used for picking.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3ResizeTb4b4b4(Te6edf3wTb4b4b4, Te6edf3h Tffa657float64Tb4b4b4) Tb4b4b4{
Tff7b72if Te6edf3w Tb4b4b4> T79c0ff0 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3width Tb4b4b4= Te6edf3w
Tb4b4b4}
Tff7b72if Te6edf3h Tb4b4b4> T79c0ff0 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3height Tb4b4b4= Te6edf3h
Tb4b4b4}
Tb4b4b4}
T8b949e// Camera returns the current view.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3CameraTb4b4b4(Tb4b4b4) Te6edf3CameraState Tb4b4b4{
Tff7b72return Te6edf3CameraStateTb4b4b4{Te6edf3XTb4b4b4: Te6edf3sTb4b4b4.Te6edf3camXTb4b4b4, Te6edf3YTb4b4b4: Te6edf3sTb4b4b4.Te6edf3camYTb4b4b4, Te6edf3ZoomTb4b4b4: Te6edf3sTb4b4b4.Te6edf3zoomTb4b4b4}
Tb4b4b4}
T8b949e// SetCamera sets pan/zoom (zoom clamped).
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3SetCameraTb4b4b4(Te6edf3xTb4b4b4, Te6edf3yTb4b4b4, Te6edf3zoom Tffa657float64Tb4b4b4) Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3camX Tb4b4b4= Te6edf3x
Te6edf3sTb4b4b4.Te6edf3camY Tb4b4b4= Te6edf3y
Tff7b72if Te6edf3zoom Tb4b4b4< T79c0ff0.05 Tb4b4b4{
Te6edf3zoom Tb4b4b4= T79c0ff0.05
Tb4b4b4}
Tff7b72if Te6edf3zoom Tb4b4b4> T79c0ff8 Tb4b4b4{
Te6edf3zoom Tb4b4b4= T79c0ff8
Tb4b4b4}
Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4= Te6edf3zoom
Tb4b4b4}
T8b949e// PanBy moves the camera in world units.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3PanByTb4b4b4(Te6edf3dxTb4b4b4, Te6edf3dy Tffa657float64Tb4b4b4) Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3camX Tff7b72+= Te6edf3dx
Te6edf3sTb4b4b4.Te6edf3camY Tff7b72+= Te6edf3dy
Tb4b4b4}
T8b949e// ZoomAt zooms around a screen point (css pixels, origin top-left).
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3ZoomAtTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4, Te6edf3factor Tffa657float64Tb4b4b4) Tb4b4b4{
Tff7b72if Te6edf3factor Tff7b72<= T79c0ff0 Tb4b4b4{
Tff7b72return
Tb4b4b4}
Te6edf3wxTb4b4b4, Te6edf3wy Tff7b72:= Te6edf3sTb4b4b4.Te6edf3screenToWorldTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4)
Te6edf3sTb4b4b4.Te6edf3zoom Tff7b72*= Te6edf3factor
Tff7b72if Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4< T79c0ff0.05 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4= T79c0ff0.05
Tb4b4b4}
Tff7b72if Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4> T79c0ff8 Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3zoom Tb4b4b4= T79c0ff8
Tb4b4b4}
Te6edf3nxTb4b4b4, Te6edf3ny Tff7b72:= Te6edf3sTb4b4b4.Te6edf3screenToWorldTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4)
Te6edf3sTb4b4b4.Te6edf3camX Tff7b72+= Te6edf3wx Tff7b72- Te6edf3nx
Te6edf3sTb4b4b4.Te6edf3camY Tff7b72+= Te6edf3wy Tff7b72- Te6edf3ny
Tb4b4b4}
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3screenToWorldTb4b4b4(Te6edf3sxTb4b4b4, Te6edf3sy Tffa657float64Tb4b4b4) Tb4b4b4(Tffa657float64Tb4b4b4, Tffa657float64Tb4b4b4) Tb4b4b4{
T8b949e// Screen centre is cam world position.
Te6edf3cx Tff7b72:= Te6edf3sTb4b4b4.Te6edf3width Tff7b72* T79c0ff0.5
Te6edf3cy Tff7b72:= Te6edf3sTb4b4b4.Te6edf3height Tff7b72* T79c0ff0.5
Te6edf3wx Tff7b72:= Te6edf3sTb4b4b4.Te6edf3camX Tff7b72+ Tb4b4b4(Te6edf3sxTff7b72-Te6edf3cxTb4b4b4)Tff7b72/Te6edf3sTb4b4b4.Te6edf3zoom
Te6edf3wy Tff7b72:= Te6edf3sTb4b4b4.Te6edf3camY Tff7b72+ Tb4b4b4(Te6edf3syTff7b72-Te6edf3cyTb4b4b4)Tff7b72/Te6edf3sTb4b4b4.Te6edf3zoom
Tff7b72return Te6edf3wxTb4b4b4, Te6edf3wy
Tb4b4b4}
T8b949e// Tick runs a few force iterations when live layout is on.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3TickTb4b4b4(Te6edf3steps Tffa657intTb4b4b4) Tb4b4b4{
Tff7b72if Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4) Tff7b72== T79c0ff0 Tb4b4b4{
Tff7b72return
Tb4b4b4}
Tff7b72if Te6edf3steps Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3steps Tb4b4b4= T79c0ff1
Tb4b4b4}
Tff7b72if Te6edf3steps Tb4b4b4> T79c0ff3 Tb4b4b4{
Te6edf3steps Tb4b4b4= T79c0ff3
Tb4b4b4}
Tff7b72if Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4) Tff7b72!= Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4) Tff7b72|| Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4) Tff7b72!= Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4) Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3vx Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float64Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Te6edf3sTb4b4b4.Te6edf3vy Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float64Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3layoutNodes Tff7b72:= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Te6edf3layoutTb4b4b4.Te6edf3NodeTb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Te6edf3n Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]
Te6edf3fixed Tff7b72:= Te6edf3nTb4b4b4.Te6edf3Fixed
Tff7b72if Te6edf3sTb4b4b4.Te6edf3dragIdx Tff7b72== Te6edf3i Tb4b4b4{
Te6edf3fixed Tb4b4b4= Tff7b72true
Tb4b4b4}
Te6edf3layoutNodesTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= Te6edf3layoutTb4b4b4.Te6edf3NodeTb4b4b4{
Te6edf3IDTb4b4b4: Te6edf3nTb4b4b4.Te6edf3IDTb4b4b4,
Te6edf3XTb4b4b4: Te6edf3nTb4b4b4.Te6edf3XTb4b4b4,
Te6edf3YTb4b4b4: Te6edf3nTb4b4b4.Te6edf3YTb4b4b4,
Te6edf3VxTb4b4b4: Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4,
Te6edf3VyTb4b4b4: Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4,
Te6edf3MassTb4b4b4: Te6edf3nTb4b4b4.Te6edf3MassTb4b4b4,
Te6edf3FixedTb4b4b4: Te6edf3fixedTb4b4b4,
Tb4b4b4}
Tb4b4b4}
Te6edf3layoutEdges Tff7b72:= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Te6edf3layoutTb4b4b4.Te6edf3EdgeTb4b4b4, T79c0ff0Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3edgesTb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3edges Tb4b4b4{
Te6edf3e Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3edgesTb4b4b4[Te6edf3iTb4b4b4]
T8b949e// WebGL node radii are ~18-48. Rest lengths must clear diameters
T8b949e// like vis-network springLength 200 does for smaller canvas glyphs.
Te6edf3length Tff7b72:= T79c0ff300.0
Tff7b72if Te6edf3eTb4b4b4.Te6edf3Width Tff7b72>= T79c0ff2.5 Tb4b4b4{
Te6edf3length Tb4b4b4= T79c0ff260
Tb4b4b4}
Te6edf3layoutEdges Tb4b4b4= Tffa657appendTb4b4b4(Te6edf3layoutEdgesTb4b4b4, Te6edf3layoutTb4b4b4.Te6edf3EdgeTb4b4b4{
Te6edf3FromTb4b4b4: Te6edf3eTb4b4b4.Te6edf3FromTb4b4b4,
Te6edf3ToTb4b4b4: Te6edf3eTb4b4b4.Te6edf3ToTb4b4b4,
Te6edf3LengthTb4b4b4: Te6edf3lengthTb4b4b4,
Tb4b4b4}Tb4b4b4)
Tb4b4b4}
T8b949e// Softer than one-shot Settle defaults so live layout does not twitch.
T8b949e// Gravity -1 disables origin pull so dragged layouts stay put.
Te6edf3_ Tb4b4b4= Te6edf3layoutTb4b4b4.Te6edf3SettleTb4b4b4(Te6edf3layoutTb4b4b4.Te6edf3RequestTb4b4b4{
Te6edf3NodesTb4b4b4: Te6edf3layoutNodesTb4b4b4,
Te6edf3EdgesTb4b4b4: Te6edf3layoutEdgesTb4b4b4,
Te6edf3IterationsTb4b4b4: Te6edf3stepsTb4b4b4,
Te6edf3GravityTb4b4b4: Tff7b72-T79c0ff1Tb4b4b4,
Te6edf3RepulsionTb4b4b4: T79c0ff1800Tb4b4b4,
Te6edf3SpringKTb4b4b4: T79c0ff0.014Tb4b4b4,
Te6edf3DampingTb4b4b4: T79c0ff0.58Tb4b4b4,
Te6edf3MaxSpeedTb4b4b4: T79c0ff6Tb4b4b4,
Tb4b4b4}Tb4b4b4)
Tff7b72const Te6edf3restSpeed Tb4b4b4= T79c0ff0.12
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Tff7b72if Te6edf3sTb4b4b4.Te6edf3dragIdx Tff7b72== Te6edf3i Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= T79c0ff0
Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= T79c0ff0
Tff7b72continue
Tb4b4b4}
Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3X Tb4b4b4= Te6edf3layoutNodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3X
Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3Y Tb4b4b4= Te6edf3layoutNodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3Y
Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= Te6edf3layoutNodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3Vx
Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= Te6edf3layoutNodesTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4.Te6edf3Vy
Tff7b72if Te6edf3mathTb4b4b4.Te6edf3HypotTb4b4b4(Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4, Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4[Te6edf3iTb4b4b4]Tb4b4b4) Tb4b4b4< Te6edf3restSpeed Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3vxTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= T79c0ff0
Te6edf3sTb4b4b4.Te6edf3vyTb4b4b4[Te6edf3iTb4b4b4] Tb4b4b4= T79c0ff0
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e// PositionsMap returns id -> xy for caching.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3PositionsMapTb4b4b4(Tb4b4b4) Tff7b72mapTb4b4b4[Tffa657stringTb4b4b4]Te6edf3layoutTb4b4b4.Te6edf3XY Tb4b4b4{
Te6edf3out Tff7b72:= Tffa657makeTb4b4b4(Tff7b72mapTb4b4b4[Tffa657stringTb4b4b4]Te6edf3layoutTb4b4b4.Te6edf3XYTb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Te6edf3n Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]
Te6edf3outTb4b4b4[Te6edf3nTb4b4b4.Te6edf3IDTb4b4b4] Tb4b4b4= Te6edf3layoutTb4b4b4.Te6edf3XYTb4b4b4{Te6edf3XTb4b4b4: Te6edf3nTb4b4b4.Te6edf3XTb4b4b4, Te6edf3YTb4b4b4: Te6edf3nTb4b4b4.Te6edf3YTb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3out
Tb4b4b4}
T8b949e// PickNearest returns the node id under a screen point, or "".
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3PickNearestTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4, Te6edf3maxDistPx Tffa657float64Tb4b4b4) Tffa657string Tb4b4b4{
Tff7b72if Te6edf3maxDistPx Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3maxDistPx Tb4b4b4= T79c0ff14
Tb4b4b4}
Te6edf3wxTb4b4b4, Te6edf3wy Tff7b72:= Te6edf3sTb4b4b4.Te6edf3screenToWorldTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4)
Te6edf3best Tff7b72:= Ta5d6ff""
Te6edf3bestD2 Tff7b72:= Te6edf3mathTb4b4b4.Te6edf3MaxFloat64
Te6edf3maxWorld Tff7b72:= Te6edf3maxDistPx Tff7b72/ Te6edf3sTb4b4b4.Te6edf3zoom
Te6edf3maxD2 Tff7b72:= Te6edf3maxWorld Tff7b72* Te6edf3maxWorld
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Te6edf3n Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]
Te6edf3dx Tff7b72:= Te6edf3nTb4b4b4.Te6edf3X Tff7b72- Te6edf3wx
Te6edf3dy Tff7b72:= Te6edf3nTb4b4b4.Te6edf3Y Tff7b72- Te6edf3wy
Te6edf3d2 Tff7b72:= Te6edf3dxTff7b72*Te6edf3dx Tff7b72+ Te6edf3dyTff7b72*Te6edf3dy
Te6edf3hitR Tff7b72:= Te6edf3nTb4b4b4.Te6edf3Size Tff7b72/ Te6edf3sTb4b4b4.Te6edf3zoom
Tff7b72if Te6edf3hitR Tb4b4b4< Te6edf3maxWorld Tb4b4b4{
Te6edf3hitR Tb4b4b4= Te6edf3maxWorld
Tb4b4b4}
Tff7b72if Te6edf3d2 Tff7b72<= Te6edf3hitRTff7b72*Te6edf3hitR Tff7b72&& Te6edf3d2 Tb4b4b4< Te6edf3bestD2 Tff7b72&& Te6edf3d2 Tff7b72<= Te6edf3maxD2Tff7b72*T79c0ff4 Tb4b4b4{
Te6edf3bestD2 Tb4b4b4= Te6edf3d2
Te6edf3best Tb4b4b4= Te6edf3nTb4b4b4.Te6edf3ID
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3best
Tb4b4b4}
T8b949e// DragStart begins dragging a node by id.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3DragStartTb4b4b4(Te6edf3id Tffa657stringTb4b4b4) Tffa657bool Tb4b4b4{
Te6edf3iTb4b4b4, Te6edf3ok Tff7b72:= Te6edf3sTb4b4b4.Te6edf3indexTb4b4b4[Te6edf3idTb4b4b4]
Tff7b72if Tb4b4b4!Te6edf3ok Tb4b4b4{
Tff7b72return Tff7b72false
Tb4b4b4}
Te6edf3sTb4b4b4.Te6edf3dragIdx Tb4b4b4= Te6edf3i
Tff7b72return Tff7b72true
Tb4b4b4}
T8b949e// DragTo moves the dragged node to a screen point.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3DragToTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenY Tffa657float64Tb4b4b4) Tb4b4b4{
Tff7b72if Te6edf3sTb4b4b4.Te6edf3dragIdx Tb4b4b4< T79c0ff0 Tff7b72|| Te6edf3sTb4b4b4.Te6edf3dragIdx Tff7b72>= Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4) Tb4b4b4{
Tff7b72return
Tb4b4b4}
Te6edf3wxTb4b4b4, Te6edf3wy Tff7b72:= Te6edf3sTb4b4b4.Te6edf3screenToWorldTb4b4b4(Te6edf3screenXTb4b4b4, Te6edf3screenYTb4b4b4)
Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3sTb4b4b4.Te6edf3dragIdxTb4b4b4]Tb4b4b4.Te6edf3X Tb4b4b4= Te6edf3wx
Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3sTb4b4b4.Te6edf3dragIdxTb4b4b4]Tb4b4b4.Te6edf3Y Tb4b4b4= Te6edf3wy
Tb4b4b4}
T8b949e// DragEnd clears the drag target.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3DragEndTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3dragIdx Tb4b4b4= Tff7b72-T79c0ff1
Tb4b4b4}
T8b949e// Counts returns node and edge lengths.
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3CountsTb4b4b4(Tb4b4b4) Tb4b4b4(Te6edf3nodesTb4b4b4, Te6edf3edges Tffa657intTb4b4b4) Tb4b4b4{
Tff7b72return Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4)Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3edgesTb4b4b4)
Tb4b4b4}
T8b949e// PackNodes fills a float32 draw buffer (len = n * NodeStride).
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3PackNodesTb4b4b4(Te6edf3dst Tb4b4b4[Tb4b4b4]Tffa657float32Tb4b4b4) Tb4b4b4[Tb4b4b4]Tffa657float32 Tb4b4b4{
Te6edf3need Tff7b72:= Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4) Tff7b72* Te6edf3NodeStride
Tff7b72if Tffa657capTb4b4b4(Te6edf3dstTb4b4b4) Tb4b4b4< Te6edf3need Tb4b4b4{
Te6edf3dst Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float32Tb4b4b4, Te6edf3needTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3dst Tb4b4b4= Te6edf3dstTb4b4b4[Tb4b4b4:Te6edf3needTb4b4b4]
Tb4b4b4}
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3nodes Tb4b4b4{
Te6edf3n Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3iTb4b4b4]
Te6edf3o Tff7b72:= Te6edf3i Tff7b72* Te6edf3NodeStride
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff0Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3XTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff1Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3YTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff2Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3SizeTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff3Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3RTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff4Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3GTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff5Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3BTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff6Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3ATb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff7Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3nTb4b4b4.Te6edf3KindTb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3dst
Tb4b4b4}
T8b949e// PackEdges fills a float32 draw buffer (len = m * EdgeStride).
Tff7b72func Tb4b4b4(Te6edf3s Tff7b72*Te6edf3SceneTb4b4b4) Te6edf3PackEdgesTb4b4b4(Te6edf3dst Tb4b4b4[Tb4b4b4]Tffa657float32Tb4b4b4) Tb4b4b4[Tb4b4b4]Tffa657float32 Tb4b4b4{
Te6edf3need Tff7b72:= Tffa657lenTb4b4b4(Te6edf3sTb4b4b4.Te6edf3edgesTb4b4b4) Tff7b72* Te6edf3EdgeStride
Tff7b72if Tffa657capTb4b4b4(Te6edf3dstTb4b4b4) Tb4b4b4< Te6edf3need Tb4b4b4{
Te6edf3dst Tb4b4b4= Tffa657makeTb4b4b4(Tb4b4b4[Tb4b4b4]Tffa657float32Tb4b4b4, Te6edf3needTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3dst Tb4b4b4= Te6edf3dstTb4b4b4[Tb4b4b4:Te6edf3needTb4b4b4]
Tb4b4b4}
Te6edf3w Tff7b72:= T79c0ff0
Tff7b72for Te6edf3i Tff7b72:= Tff7b72range Te6edf3sTb4b4b4.Te6edf3edges Tb4b4b4{
Te6edf3e Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3edgesTb4b4b4[Te6edf3iTb4b4b4]
Te6edf3aiTb4b4b4, Te6edf3okA Tff7b72:= Te6edf3sTb4b4b4.Te6edf3indexTb4b4b4[Te6edf3eTb4b4b4.Te6edf3FromTb4b4b4]
Te6edf3biTb4b4b4, Te6edf3okB Tff7b72:= Te6edf3sTb4b4b4.Te6edf3indexTb4b4b4[Te6edf3eTb4b4b4.Te6edf3ToTb4b4b4]
Tff7b72if Tb4b4b4!Te6edf3okA Tff7b72|| Tb4b4b4!Te6edf3okB Tb4b4b4{
Tff7b72continue
Tb4b4b4}
Te6edf3a Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3aiTb4b4b4]
Te6edf3b Tff7b72:= Tff7b72&Te6edf3sTb4b4b4.Te6edf3nodesTb4b4b4[Te6edf3biTb4b4b4]
Te6edf3o Tff7b72:= Te6edf3w Tff7b72* Te6edf3EdgeStride
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff0Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3aTb4b4b4.Te6edf3XTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff1Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3aTb4b4b4.Te6edf3YTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff2Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3bTb4b4b4.Te6edf3XTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff3Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3bTb4b4b4.Te6edf3YTb4b4b4)
Te6edf3rTb4b4b4, Te6edf3gTb4b4b4, Te6edf3blTb4b4b4, Te6edf3al Tff7b72:= Te6edf3eTb4b4b4.Te6edf3RTb4b4b4, Te6edf3eTb4b4b4.Te6edf3GTb4b4b4, Te6edf3eTb4b4b4.Te6edf3BTb4b4b4, Te6edf3eTb4b4b4.Te6edf3A
Tff7b72if Te6edf3al Tff7b72<= T79c0ff0 Tb4b4b4{
Te6edf3al Tb4b4b4= T79c0ff0.45
Tb4b4b4}
Tff7b72if Te6edf3r Tff7b72== T79c0ff0 Tff7b72&& Te6edf3g Tff7b72== T79c0ff0 Tff7b72&& Te6edf3bl Tff7b72== T79c0ff0 Tb4b4b4{
Te6edf3rTb4b4b4, Te6edf3gTb4b4b4, Te6edf3bl Tb4b4b4= T79c0ff0.45Tb4b4b4, T79c0ff0.45Tb4b4b4, T79c0ff0.55
Tb4b4b4}
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff4Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3rTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff5Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3gTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff6Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3blTb4b4b4)
Te6edf3dstTb4b4b4[Te6edf3oTff7b72+T79c0ff7Tb4b4b4] Tb4b4b4= Tffa657float32Tb4b4b4(Te6edf3alTb4b4b4)
Te6edf3wTff7b72++
Tb4b4b4}
Tff7b72return Te6edf3dstTb4b4b4[Tb4b4b4:Te6edf3wTff7b72*Te6edf3EdgeStrideTb4b4b4]
Tb4b4b4}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────